package com.luo.demo.scg.client.config;

import com.nimbusds.jose.shaded.json.JSONArray;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.server.resource.authentication.AbstractOAuth2TokenAuthenticationToken;
import org.springframework.security.web.server.authorization.AuthorizationContext;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * OAuth2 Resource Server鉴权管理器
 *
 * @author luohq
 * @date 2022-03-26
 */
@Component
@Slf4j
public class ResourceApiAuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {

    @Override
    public Mono<AuthorizationDecision> check(Mono<Authentication> mono, AuthorizationContext authorizationContext) {
        /** 获取当前请求信息 */
        ServerHttpRequest request = authorizationContext.getExchange().getRequest();
        String path = request.getPath().value();
        String method = request.getMethodValue();
        log.info("{}cur request path: {}, method: {}", authorizationContext.getExchange().getLogPrefix(), path, method);

        /** 查询当前path对应的scope权限 */
        List<String> authorities = this.getAllowedAuthorities(path);

        /** 判断当前用户access_token.scopes是否包当前请求允许的scope */
        return mono
                //认证是否通过（即JWT签名、过期时间等是否合法）
                //.filter(Authentication::isAuthenticated)
                .filter(authentication -> {
                    log.info("{}cur request authentication class: {}", authorizationContext.getExchange().getLogPrefix(), authentication.getClass().getName());
                    //转换OAuth Authentication（JwtAuthenticationToken | BearerTokenAuthentication）
                    AbstractOAuth2TokenAuthenticationToken oauth2AuthenticationToken = (AbstractOAuth2TokenAuthenticationToken) authentication;
                    //提取token中的claims
                    Map<String, Object> claims = oauth2AuthenticationToken.getTokenAttributes();
                    log.info("{}cur request access_token claims: {}", authorizationContext.getExchange().getLogPrefix(), claims);
                    //提取scope列表，在Authentication.authorities中会默认添加SCOPE_前缀
                    List<String> scopeList = this.convertScope(claims);
                    log.info("{}cur request access_token scope: {}", authorizationContext.getExchange().getLogPrefix(), scopeList);

                    /** 当前认证已通过 */
                    return authentication.isAuthenticated();
                })
                //提取令牌中包含的权限信息（即JWT scope）
                .flatMapIterable(Authentication::getAuthorities)
                //提取权限字符串
                .map(GrantedAuthority::getAuthority)
                //当前path允许访问的权限范围是否包含令牌中的权限
                .any(authorities::contains)
                //若满足权限要求则授权通过
                .map(AuthorizationDecision::new)
                //否则授权不通过
                .defaultIfEmpty(new AuthorizationDecision(false));
    }


    private List<String> getAllowedAuthorities(String requestPath) {
        //TODO 查询当前path对应的scope权限
        return Arrays.asList("SCOPE_" + "articles.read");
    }
    /**
     * 提取access_token.claims.scope列表
     *
     * @param claims access_token.claims
     * @return access_token.claims.scope列表
     */
    private List<String> convertScope(Map<String, Object> claims) {
        //提取claims.scope
        Object scopeObj = claims.get("scope");

        /** OpaqueTokens时直接返回List */
        if (scopeObj instanceof List) {
            return (List) scopeObj;
        }

        /** Jwt时直接转换JSONArray为List */
        //提取claims.scope
        JSONArray scope = (JSONArray) claims.get("scope");
        //提取scope列表，在Authentication.authorities中会默认添加SCOPE_前缀
        //例如scope包含articles.read，则对应此处Authentication.authorities为SCOPE_articles.read
        List<String> scopeList = Arrays.asList(scope.toArray(new String[scope.size()]));
        return scopeList;
    }

}